#ifndef STX_PLATFORM_POSIX_THREAD_HPP
#define STX_PLATFORM_POSIX_THREAD_HPP

#include <stx/platform/posix/common.hpp>
#include <stx/platform/posix/thread/common.hpp>
#include <stx/platform/posix/thread/thread_attributes.hpp>
#include <stx/platform/posix/thread/mutex_attributes.hpp>
#include <stx/platform/posix/thread/mutex.hpp>
#include <stx/platform/posix/thread/read_write_lock_attributes.hpp>
#include <stx/platform/posix/thread/read_write_lock.hpp>
#include <stx/platform/posix/thread/conditional_variable_attributes.hpp>
#include <stx/platform/posix/thread/conditional_variable.hpp>
#include <stx/platform/posix/thread/barrier_attributes.hpp>
#include <stx/platform/posix/thread/barrier.hpp>
#include <stx/platform/posix/thread/spin_lock.hpp>
#include <stx/platform/posix/thread/once.hpp>
#include <stx/platform/posix/thread/this_thread.hpp>
#include <stx/platform/posix/thread/thread_key.hpp>

#ifdef STX_PLATFORM_QNX_NEUTRINO
//  On QNX pthread_atfork() is declared in <process.h>, not in <pthread.h>.
#include <process.h>
#endif

namespace stx {

namespace posix {

#if STX_HAS_PTHREAD_ATFORK

inline void at_fork(void (*prepare)(void), void (*parent)(void), void (*child)(void))
{
    int ret = pthread_atfork(prepare, parent, child);
    if (ret) {
        STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_atfork");
    }
}

#endif

template<class CallableType>
inline void* thread_routine(void* arg)
{
    (*((CallableType*) arg))();
    return NULL;
}

//  Create thread object for main thread:
//      thread t(this_thread::handle());

class thread
{
public:
    
    typedef thread this_type;
    
    typedef pthread_t handle_type;
    
    handle_type handle()
    {
        return handle_;
    }
    
    const handle_type handle() const
    {
        return handle_;
    }
    
    thread()
    {
    }
    
    thread(const pthread_t handle): handle_(handle)
    {
    }
    
    thread(const thread& t)
    {
        handle_ = t.handle();
    }
    
    ~thread()
    {
    }
    
    void create(void *(*start_routine)(void*), void* arg)
    {
        int ret = pthread_create(&handle_, NULL, start_routine, arg);
        if (ret) {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_create");
        }
    }
    
    void create(
        const thread_attributes& attr,
        void *(*start_routine)(void*),
        void* arg)
    {
        int ret = pthread_create(&handle_, attr.handle(), start_routine, arg);
        if (ret) {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_create");
        }
    }
    
    template<class CallableType>
    void create(CallableType callable)
    {
        create(thread_routine<CallableType>, (void*) &callable);
    }
    
    template<class CallableType>
    void create(const thread_attributes& attr, CallableType callable)
    {
        create(attr, thread_routine<CallableType>, (void*) &callable);
    }
    
    void cancel()
    {
        int ret = pthread_cancel(handle_);
        if (ret) {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_cancel");
        }
    }
    
    void join()
    {
        void* val;
        join(val);
    }
    
    void join(void*& val)
    {
        int ret = pthread_join(handle_, &val);
        if (ret) {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_join");
        }
    }
    
    bool joinable() const
    {
        return !equal(this_thread::handle());
    }
    
    static unsigned hardware_concurrency()
    {
        return 0;
    }
    
    bool equal(const thread& t) const
    {
        return pthread_equal(handle_, t.handle());
    }
    
    bool equal(const handle_type& h) const
    {
        return pthread_equal(handle_, h);
    }
    
    bool operator ==(const thread& other) const
    {
        return equal(other);
    }
    
    void detach()
    {
        int ret = pthread_detach(handle_);
        if (ret) {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_detach");
        }
    }
    
    void swap(thread& t)
    {
        handle_type h = handle_;
        handle_ = t.handle_;
        t.handle_ = h;
    }
    
    //  policy:
    //      SCHED_FIFO
    //      SCHED_RR
    //      SCHED_SPORADIC
    //      SCHED_OTHER
    //  The SCHED_FIFO and SCHED_RR policies shall have a single scheduling parameter: sched_priority.
    void get_scheduling_parameters(int& policy, struct sched_param& param) const
    {
        int ret = pthread_getschedparam(handle_, &policy, &param);
        if (ret) {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_getschedparam");
        }
    }
    
    this_type& set_scheduling_parameters(int policy, const struct sched_param& param)
    {
        int ret = pthread_setschedparam(handle_, policy, &param);
        if (ret) {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_setschedparam");
        }
        return *this;
    }
    
    int scheduling_policy() const
    {
        int policy;
        struct sched_param param;
        get_scheduling_parameters(policy, param);
        return policy;
    }
    
    this_type& set_scheduling_policy(int policy)
    {
        int old_policy;
        struct sched_param param;
        get_scheduling_parameters(old_policy, param);
        set_scheduling_parameters(policy, param);
        return *this;
    }
    
    int scheduling_priority() const
    {
        int policy;
        struct sched_param param;
        get_scheduling_parameters(policy, param);
        return param.sched_priority;
    }
    
    #if STX_HAS_PTHREAD_SETSCHEDPRIO
    this_type& set_scheduling_priority(int priority)
    {
        int ret = pthread_setschedprio(handle_, priority);
        if (ret) {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_setschedprio");
        }
        return *this;
    }
    #else
    this_type& set_scheduling_priority(int priority)
    {
        int policy;
        struct sched_param param;
        get_scheduling_parameters(policy, param);
        param.sched_priority = priority;
        set_scheduling_parameters(policy, param);
        return *this;
    }
    #endif
    
    #if STX_HAS_PTHREAD_GETCPUCLOCKID
    clockid_t cpu_clock_id() const
    {
        clockid_t clock_id;
        int ret = pthread_getcpuclockid(handle_, &clock_id);
        if (ret) {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_getcpuclockid");
        }
        return clock_id;
    }
    #endif
    
    void kill(int signal) const
    {
        int ret = pthread_kill(handle_, signal);
        if (ret) {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_kill");
        }
    }
    
    void send_signal(int signal)
    {
        kill(signal);
    }
    
private:
    
    handle_type handle_;
};

} // namespace posix

} // namespace stx

#endif // STX_PLATFORM_POSIX_THREAD_HPP
